#pragma once
#include<iostream>
#include<unordered_map>
#include<QMutex>
#include"IO.h"
#include"ResourceInfo.h"
#include"RangeEle.h"
using namespace std;

class ColInfo{
    string colName;
    TYPE dataType;
    bool isWithIndex;
    vector<double> rangeLeft;

    void init(const string& colName, const TYPE& dataType, const bool& isWithIndex, const vector<double>& rangeVec){
        this->colName = colName;
        this->dataType = dataType;
        this->isWithIndex = isWithIndex;
        this->rangeLeft = rangeVec;
    }

    //返回下标不返回具体值
    int binarySearch(const double& val) const {
        int left = 0, right = rangeLeft.size() - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;        //防止溢出
            double midVal = rangeLeft[mid];
            if(midVal == val){
                return mid;
            }
            else if(midVal < val){
                left = mid + 1;
            }
            else{
                right = mid - 1;
            }
        }
        return right;
    }

    static vector<double> getAvgRangeLeft(const double& leftInterval, const double& rightInterval, const int& count){
        const double& rangeLen = (abs(rightInterval) / count) + (abs(leftInterval) / count);  //分开算 防溢出
        double tmpLeft = leftInterval;
        vector<double> rangeLeft;
        for(int i = 0; i < count; i++, tmpLeft += rangeLen){
            rangeLeft.push_back(tmpLeft);
        }
        return rangeLeft;
    }


public:

    ColInfo(const string& colName, const TYPE& dataType, const bool& isWithIndex, const vector<double>& rangeVec){
        init(colName, dataType, isWithIndex, rangeVec);
    }

    ColInfo(const string& colName, const TYPE& dataType, const bool& isWithIndex, const double& leftInterval, const double& rightInterval, const int& partitionCount){
        init(colName, dataType, isWithIndex, getAvgRangeLeft(leftInterval, rightInterval, partitionCount));
    }

    ~ColInfo(){}

    string getColName() const {
        return colName;
    }

    TYPE getDataType() const {
        return dataType;
    }

    bool getIsWithIndex() const {
        return isWithIndex;
    }


    double getProbRangeLeft(const double& val) const {
        return rangeLeft[binarySearch(val)];
    }

    vector<double> getMultiProbRangeLeft(const double& val, const bool& isLeftRange) const {
        const int& ind = binarySearch(val);
        if(isLeftRange){
            return vector<double>(rangeLeft.begin(), rangeLeft.begin() + ind + 1);
        }
        return vector<double>(rangeLeft.begin() + ind, rangeLeft.end());
    }

    static ColInfo* getColInfoWithStr(const string& strData){
        const vector<string>& strVec = StringUtil::splitStr(strData, ',');
        vector<double> rangeVec;
        for(size_t i = 3; i < strVec.size(); i++){
            rangeVec.push_back(stod(strVec[i]));
        }
        return new ColInfo(strVec[0], (TYPE)stoi(strVec[1]), (bool)stoi(strVec[2]), rangeVec);
    }

    static string getStrWithColInfo(const ColInfo* colInfo){
        string resStr;
        resStr.append(colInfo->colName + ',');
        resStr.append(to_string(colInfo->dataType) + ',');
        resStr.append(to_string(colInfo->isWithIndex) + ',');
        for(const double& rangeLeft : colInfo->rangeLeft){
            resStr.append(to_string(rangeLeft) + ",");
        }
        return resStr.substr(0, resStr.length() - 1);
    }
};


//只读模式 操作不用加锁
class TableInfo{
private:
    string baseName;
    string tableName;
    int16_t blockCount;
    map<string, ColInfo*> colInfoStore;

public:

    TableInfo(const string& baseName, const string& tableName, const int16_t& blockCount, const vector<ColInfo*>& colInfoVec){
        this->baseName = baseName;
        this->tableName = tableName;
        this->blockCount = blockCount;
        for(ColInfo* colInfo : colInfoVec){
            this->colInfoStore.insert(make_pair(colInfo->getColName(), colInfo));
        }
    }

    ~TableInfo(){
        for(auto& it : colInfoStore){
            MemUtil::clearPtrMem(it.second);
        }
    }

    string getBaseName() const {
        return baseName;
    }

    string getTableName() const {
        return tableName;
    }


    size_t getColCount() const {
        return colInfoStore.size();
    }

    int16_t getBlockCount() const {
        return blockCount;
    }

    int16_t getRandomBlockNo() const {
        return rand() % this->blockCount;
    }

                                   //false： 所有列     true： 带索引的列
    vector<string> getColNameVec(const bool& isIndCol) const {
        vector<string> colNameVec;
        for(const auto& it : colInfoStore){
            ColInfo* colInfo = it.second;
            //要所有的列或有索引
            if(!isIndCol || colInfo->getIsWithIndex()){
                colNameVec.push_back(colInfo->getColName());
            }
        }
        //返回按字典序排序的列名vec
        return colNameVec;
    }

    ColInfo* getColInfo(const string& colName) const {
        const auto& it = colInfoStore.find(colName);
        if(it == colInfoStore.end()){
            throw string("no such col");
        }
        return it->second;
    }

    bool isColWithInd(const string& colName) const {
        return getColInfo(colName)->getIsWithIndex();
    }

    vector<ResourceInfo> getColBlockResInfo(const string& colName) const {
        vector<ResourceInfo> colBlockResInfo;
        const TYPE& dataType = colInfoStore.at(colName)->getDataType();
        for(int16_t blockNo = 0; blockNo < blockCount; blockNo++){
            colBlockResInfo.push_back(ResourceInfo(baseName, tableName, colName, blockNo, dataType));
        }
        return colBlockResInfo;
    }
    
    ResourceInfo getProbIndResInfo(const string& colName, const double& val) const {
        ColInfo* colInfo = getColInfo(colName);
        return ResourceInfo(baseName, tableName, colName, colInfo->getDataType(), colInfo->getProbRangeLeft(val));
    }

    ResourceInfo getProbBitMapResInfo(const string& colName, const double& val) const {
        ColInfo* colInfo = getColInfo(colName);
        return ResourceInfo(baseName, tableName, colName, colInfo->getProbRangeLeft(val));
    }

    vector<ResourceInfo> getRangeProbIndResInfo(const string& colName, const double& val, const bool& isLeftRange) const {
        ColInfo* colInfo = getColInfo(colName);
        const TYPE& dataType = colInfo->getDataType();
        const vector<double>& rangeLeftVec = colInfo->getMultiProbRangeLeft(val, isLeftRange);
        vector<ResourceInfo> resInfoVec;
        for(const double& rangeLeft : rangeLeftVec){
            resInfoVec.push_back(ResourceInfo(baseName, tableName, colName, dataType, rangeLeft));
        }
        return resInfoVec;
    }


    static string getFilePath(const string& baseName, const string& tableName){
        return IOUtil::getTotalPath() + "\\" + baseName + '_' + tableName + "_Info.txt";
    }

    static TableInfo* getTableInfoWithStr(const string& strData){
        const vector<string>& strVec = StringUtil::splitStr(strData, ';');
        vector<ColInfo*> colInfoVec;
        for(size_t i = 3; i < strVec.size(); i++){
            colInfoVec.push_back(ColInfo::getColInfoWithStr(strVec[i]));
        }
        return new TableInfo(strVec[0], strVec[1], stoi(strVec[2]), colInfoVec);
    }

    static string getStrWithTableInfo(const TableInfo* tableInfo){
        string resStr;
        resStr.append(tableInfo->baseName + ';');
        resStr.append(tableInfo->tableName + ';');
        resStr.append(to_string(tableInfo->blockCount) + ';');
        for(const auto& it : tableInfo->colInfoStore){
            resStr.append(ColInfo::getStrWithColInfo(it.second) + ";");
        }
        return resStr.substr(0, resStr.length() - 1);
    }
};



class TableInfoStore{
private:

    unordered_map<string, TableInfo*> store;
    QMutex mutex;

public:

    ~TableInfoStore(){
        for(auto it : store){
            MemUtil::clearPtrMem(it.second);
        }
    }

    TableInfo* getTableInfo(const string& baseName, const string& tableName){
        TableInfo* tableInfo = nullptr;
        const string& keyStr = baseName + tableName;
        mutex.lock();
        const auto& it = store.find(keyStr);
        if(it == store.end()){
            tableInfo = TableInfo::getTableInfoWithStr(IOUtil::charRead(TableInfo::getFilePath(baseName, tableName)));
            store.insert(make_pair(keyStr, tableInfo));
        }
        else{
            tableInfo = it->second;
        }
        mutex.unlock();
        return tableInfo;
    }

    void putTableInfo(TableInfo* tableInfo){
        const string& baseName = tableInfo->getBaseName(), tableName = tableInfo->getTableName();
        const string& keyStr = baseName + tableName;
        mutex.lock();
        if(store.find(keyStr) != store.end()){ throw string("table already exists"); }
        store.insert(make_pair(keyStr, tableInfo));
        IOUtil::charWrite(TableInfo::getStrWithTableInfo(tableInfo), TableInfo::getFilePath(baseName, tableName));
        mutex.unlock();
    }

};



class TableInfoManager{
private:
    static TableInfoManager* manager;

    //分区存储 上锁 增加并发
    RangeEle<TableInfoStore*>* rangeStores;

    hash<string> hashUtil;

    vector<TableInfoStore*> getStoreVec(const int& count){
        vector<TableInfoStore*> storeVec;
        for(int i = 0; i < count; i++){
            storeVec.push_back(new TableInfoStore());
        }
        return storeVec;
    }

    static TableInfoStore* getProbStore(const string& baseName, const string& tableName){
        const uint64_t& hashVal = manager->hashUtil(baseName + tableName);
        return manager->rangeStores->getProbEle(hashVal);
    }


public:

    TableInfoManager(const int& partitionCount){
        this->rangeStores = new RangeEle<TableInfoStore*>(getStoreVec(partitionCount), partitionCount);
    }

    ~TableInfoManager(){
        MemUtil::clearPtrMem(rangeStores);
    }

    static void initManager(const int& partitionCount){
        manager = new TableInfoManager(partitionCount);
    }

    static TableInfo* getTableInfo(const string& baseName, const string& tableName){
        TableInfoStore* store = getProbStore(baseName, tableName);
        return store->getTableInfo(baseName, tableName);
    }

    static void putTableInfo(TableInfo* tableInfo){
        TableInfoStore* store = getProbStore(tableInfo->getBaseName(), tableInfo->getTableName());
        store->putTableInfo(tableInfo);
    }

};
